


use pest::{iterators::Pair, Parser, error::Error as PestError};
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "src/lexer/sysy.pest"]
pub struct SysYParser;

#[derive(Debug, Clone, PartialEq)]
pub enum Token {
    Const,
    Int,
    Void,
    If,
    Else,
    While,
    Break,
    Continue,
    Return,
    
    Plus,
    Minus,
    Mul,
    Div,
    Mod,
    Assign,
    Eq,
    Neq,
    Lt,
    Gt,
    Le,
    Ge,
    Not,
    And,
    Or,
    
    LParen,
    RParen,
    LBrace,
    RBrace,
    LBrackt,
    RBrackt,
    Comma,
    Semicolon,
    
    Ident(String),
    IntegerConst(String),
}

#[derive(Debug, Clone)]
pub struct TokenWithPos {
    pub token: Token,
    pub line: usize,
    pub column: usize,
    pub text: String,
}

#[derive(Debug, Clone)]
pub struct LexError {
    pub line: usize,
    pub column: usize,
    pub message: String,
}

pub struct Lexer {
    tokens: Vec<TokenWithPos>,
    errors: Vec<LexError>,
    current: usize,
}

impl Lexer {
    pub fn new(input: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let mut tokens = Vec::new();
        let mut errors = Vec::new();
        
        match SysYParser::parse(Rule::program, input) {
            Ok(pairs) => {
                for pair in pairs {
                    match pair.as_rule() {
                        Rule::program => {
                            for inner_pair in pair.into_inner() {
                                match inner_pair.as_rule() {
                                    Rule::token => {
                                        let token_with_pos = Self::parse_token_with_pos(inner_pair);
                                        tokens.push(token_with_pos);
                                    }
                                    Rule::WHITESPACE | Rule::COMMENT => {
                                       
                                    }

                                    _ => {
                                    }
                                }
                            }
                        }
                        _ => {
                        }
                    }
                }
            }
            Err(pest_error) => {
                // 已经使用过的方法：报错后不直接离开，而是继续走，似乎不是问题所在
                let (line, column) = match pest_error.location {
                    pest::error::InputLocation::Pos(pos) => {
                        let mut line = 1;
                        let mut column = 1;
                        for (i, ch) in input.char_indices() {
                            if i >= pos {
                                break;
                            }
                            if ch == '\n' {
                                line += 1;
                                column = 1;
                            } else {
                                column += 1;
                            }
                        }
                        (line, column)
                    }
                    pest::error::InputLocation::Span((start, _)) => {
                        let mut line = 1;
                        let mut column = 1;
                        for (i, ch) in input.char_indices() {
                            if i >= start {
                                break;
                            }
                            if ch == '\n' {
                                line += 1;
                                column = 1;
                            } else {
                                column += 1;
                            }
                        }
                        (line, column)
                    }
                };
                
                let message = "Unexpected token".to_string();
                
                errors.push(LexError {
                    line,
                    column,
                    message,
                });
            }
        }
        
        Ok(Lexer {
            tokens,
            errors,
            current: 0,
        })
    }
    
    fn parse_token_with_pos(pair: Pair<Rule>) -> TokenWithPos {
        let span = pair.as_span();
        let text = pair.as_str().to_string();
        let line = span.start_pos().line_col().0;
        let column = span.start_pos().line_col().1;
        
        for inner_pair in pair.into_inner() {
            let token = match inner_pair.as_rule() {
                Rule::CONST => Token::Const,
                Rule::INT => Token::Int,
                Rule::VOID => Token::Void,
                Rule::IF => Token::If,
                Rule::ELSE => Token::Else,
                Rule::WHILE => Token::While,
                Rule::BREAK => Token::Break,
                Rule::CONTINUE => Token::Continue,
                Rule::RETURN => Token::Return,
                
                Rule::PLUS => Token::Plus,
                Rule::MINUS => Token::Minus,
                Rule::MUL => Token::Mul,
                Rule::DIV => Token::Div,
                Rule::MOD => Token::Mod,
                Rule::ASSIGN => Token::Assign,
                Rule::EQ => Token::Eq,
                Rule::NEQ => Token::Neq,
                Rule::LT => Token::Lt,
                Rule::GT => Token::Gt,
                Rule::LE => Token::Le,
                Rule::GE => Token::Ge,
                Rule::NOT => Token::Not,
                Rule::AND => Token::And,
                Rule::OR => Token::Or,
                
                Rule::L_PAREN => Token::LParen,
                Rule::R_PAREN => Token::RParen,
                Rule::L_BRACE => Token::LBrace,
                Rule::R_BRACE => Token::RBrace,
                Rule::L_BRACKT => Token::LBrackt,
                Rule::R_BRACKT => Token::RBrackt,
                Rule::COMMA => Token::Comma,
                Rule::SEMICOLON => Token::Semicolon,
                
                Rule::IDENT => Token::Ident(inner_pair.as_str().trim().to_string()),
                Rule::INTEGER_CONST => Token::IntegerConst(inner_pair.as_str().to_string()),
                
                _ => unreachable!("Unexpected inner rule: {:?}", inner_pair.as_rule()),
            };
            
            return TokenWithPos {
                token,
                line,
                column,
                text,
            };
        }
        unreachable!("No inner rule found in token")
    }
    
    fn parse_token(pair: Pair<Rule>) -> Token {
        for inner_pair in pair.into_inner() {
            match inner_pair.as_rule() {
                Rule::CONST => return Token::Const,
                Rule::INT => return Token::Int,
                Rule::VOID => return Token::Void,
                Rule::IF => return Token::If,
                Rule::ELSE => return Token::Else,
                Rule::WHILE => return Token::While,
                Rule::BREAK => return Token::Break,
                Rule::CONTINUE => return Token::Continue,
                Rule::RETURN => return Token::Return,
                
                Rule::PLUS => return Token::Plus,
                Rule::MINUS => return Token::Minus,
                Rule::MUL => return Token::Mul,
                Rule::DIV => return Token::Div,
                Rule::MOD => return Token::Mod,
                Rule::ASSIGN => return Token::Assign,
                Rule::EQ => return Token::Eq,
                Rule::NEQ => return Token::Neq,
                Rule::LT => return Token::Lt,
                Rule::GT => return Token::Gt,
                Rule::LE => return Token::Le,
                Rule::GE => return Token::Ge,
                Rule::NOT => return Token::Not,
                Rule::AND => return Token::And,
                Rule::OR => return Token::Or,
                
                Rule::L_PAREN => return Token::LParen,
                Rule::R_PAREN => return Token::RParen,
                Rule::L_BRACE => return Token::LBrace,
                Rule::R_BRACE => return Token::RBrace,
                Rule::L_BRACKT => return Token::LBrackt,
                Rule::R_BRACKT => return Token::RBrackt,
                Rule::COMMA => return Token::Comma,
                Rule::SEMICOLON => return Token::Semicolon,
                
                Rule::IDENT => return Token::Ident(inner_pair.as_str().to_string()),
                Rule::INTEGER_CONST => return Token::IntegerConst(inner_pair.as_str().to_string()),
                
                _ => unreachable!("Unexpected inner rule: {:?}", inner_pair.as_rule()),
            }
        }
        unreachable!("No inner rule found in token")
    }
    
    pub fn next(&mut self) -> Option<&TokenWithPos> {
        if self.current < self.tokens.len() {
            let token = &self.tokens[self.current];
            self.current += 1;
            Some(token)
        } else {
            None
        }
    }
    
    pub fn peek(&self) -> Option<&TokenWithPos> {
        if self.current < self.tokens.len() {
            Some(&self.tokens[self.current])
        } else {
            None
        }
    }
    
    pub fn reset(&mut self) {
        self.current = 0;
    }
    
    pub fn tokens(&self) -> &[TokenWithPos] {
        &self.tokens
    }
    
    pub fn has_next(&self) -> bool {
        self.current < self.tokens.len()
    }
    
    pub fn errors(&self) -> &[LexError] {
        &self.errors
    }
    
    pub fn has_errors(&self) -> bool {
        !self.errors.is_empty()
    }
}

impl Token {
    pub fn to_string(&self) -> String {
        match self {
            Token::Const => "CONST const".to_string(),
            Token::Int => "INT int".to_string(),
            Token::Void => "VOID void".to_string(),
            Token::If => "IF if".to_string(),
            Token::Else => "ELSE else".to_string(),
            Token::While => "WHILE while".to_string(),
            Token::Break => "BREAK break".to_string(),
            Token::Continue => "CONTINUE continue".to_string(),
            Token::Return => "RETURN return".to_string(),
            
            Token::Plus => "PLUS +".to_string(),
            Token::Minus => "MINUS -".to_string(),
            Token::Mul => "MUL *".to_string(),
            Token::Div => "DIV /".to_string(),
            Token::Mod => "MOD %".to_string(),
            Token::Assign => "ASSIGN =".to_string(),
            Token::Eq => "EQ ==".to_string(),
            Token::Neq => "NEQ !=".to_string(),
            Token::Lt => "LT <".to_string(),
            Token::Gt => "GT >".to_string(),
            Token::Le => "LE <=".to_string(),
            Token::Ge => "GE >=".to_string(),
            Token::Not => "NOT !".to_string(),
            Token::And => "AND &&".to_string(),
            Token::Or => "OR ||".to_string(),
            
            Token::LParen => "L_PAREN (".to_string(),
            Token::RParen => "R_PAREN )".to_string(),
            Token::LBrace => "L_BRACE {".to_string(),
            Token::RBrace => "R_BRACE }".to_string(),
            Token::LBrackt => "L_BRACKT [".to_string(),
            Token::RBrackt => "R_BRACKT ]".to_string(),
            Token::Comma => "COMMA ,".to_string(),
            Token::Semicolon => "SEMICOLON ;".to_string(),
            
            Token::Ident(name) => format!("IDENT {}", name),
            Token::IntegerConst(value) => {
                
                if value.starts_with("0x") || value.starts_with("0X") {
                    if let Ok(num) = i64::from_str_radix(&value[2..], 16) {
                        format!("INTEGER_CONST {}", num)
                    } else {
                        format!("INTEGER_CONST {}", value)
                    }
                } else if value.starts_with("0") && value.len() > 1 {
                    if let Ok(num) = i64::from_str_radix(&value[1..], 8) {
                        format!("INTEGER_CONST {}", num)
                    } else {
                        format!("INTEGER_CONST {}", value)
                    }
                } else {
                    format!("INTEGER_CONST {}", value)
                }
            },
        }
    }
}
